﻿#region License

//  Copyright 2014 Foundation Team
//   
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//   
//        http://www.apache.org/licenses/LICENSE-2.0
//   
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// 
//   ----------------------------------------修改标识---------------------------------------
//   修改人：  LuoZhiHui 2014/10/04
//   修改描述：

#endregion

#region 导入名称空间

using System;
using System.IO;
using System.Linq.Expressions;
using Common.Logging;
using Common.Logging.Configuration;
using Common.Logging.Factory;
using NameValueCollection = LightWork.Autofac.TypeConversion.NameValueCollection;

#endregion

namespace LightWork.Components
{
    #region 日志适配器工厂定义

    /// <summary>
    /// Log4Net 日志适配器
    /// </summary>
    [Serializable]
    public class Log4NetLoggerFactoryAdapter : AbstractCachingLoggerFactoryAdapter
    {
        private readonly ILog4NetRuntime _runtime;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="properties">configuration properties, see <see cref="Log4NetLoggerFactoryAdapter" /> for more.</param>
        public Log4NetLoggerFactoryAdapter(NameValueCollection properties)
            : this(properties, new Log4NetRuntime())
        {
        }

        /// <summary>
        /// 构造函数接受配置属性和任意
        /// <see cref="ILog4NetRuntime" /> 实例
        /// </summary>
        /// <param name="properties">configuration properties, see <see cref="Log4NetLoggerFactoryAdapter" /> for more.</param>
        /// <param name="runtime">a log4net runtime adapter</param>
        protected Log4NetLoggerFactoryAdapter(Common.Logging.Configuration.NameValueCollection properties,
            ILog4NetRuntime runtime)
            : base(true)
        {
            if (runtime == null)
                throw new ArgumentNullException(nameof(runtime));

            var properties1 = properties;
            _runtime = runtime;
            // parse config properties
            var configType = ArgUtils.GetValue(properties1, "configType", string.Empty).ToUpper();
            var configFile = ArgUtils.GetValue(properties1, "configFile", string.Empty);

            // app-relative path?
            if (configFile.StartsWith("~/") || configFile.StartsWith("~\\"))
                configFile = string.Format("{0}/{1}", AppDomain.CurrentDomain.BaseDirectory.TrimEnd('/', '\\'),
                    configFile.Substring(2));
            if (configType == "FILE" || configType == "FILE-WATCH")
            {
                if (configFile == string.Empty)
                    throw new ConfigurationException(
                        "Configuration property 'configFile' must be set for log4Net configuration of type 'FILE' or 'FILE-WATCH'.");
                if (!File.Exists(configFile))
                    throw new ConfigurationException("log4net configuration file '" + configFile + "' does not exists");
            }
            switch (configType)
            {
                case "INLINE":
                    _runtime.XmlConfiguratorConfigure();
                    break;
                case "FILE":
                    _runtime.XmlConfiguratorConfigure(configFile);
                    break;
                case "FILE-WATCH":
                    _runtime.XmlConfiguratorConfigureAndWatch(configFile);
                    break;
                case "EXTERNAL":
                    // Log4net 不通过 Foundation.Logging 完成配置
                    break;
                default:
                    _runtime.BasicConfiguratorConfigure();
                    break;
            }
        }

        /// <summary>
        /// 根据名称创建 ILog 实例
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        protected override ILog CreateLogger(string name)
        {
            return new Log4NetLogger(_runtime.GetLogger(name));
        }

        #region Nested type: ILog4NetRuntime

        /// <summary>
        /// Abstract interface to the underlying log4net runtime
        /// </summary>
        public interface ILog4NetRuntime
        {
            /// <summary>Calls log4net.Config.XmlConfigurator.Configure()" /></summary>
            void XmlConfiguratorConfigure();

            /// <summary>Calls log4net.Config.XmlConfigurator.Configure(System.IO.FileInfo)" /></summary>
            void XmlConfiguratorConfigure(string configFile);

            /// <summary>Calls log4net.Config.XmlConfigurator.ConfigureAndWatch(System.IO.FileInfo)" /></summary>
            void XmlConfiguratorConfigureAndWatch(string configFile);

            /// <summary>Calls log4net.Config.BasicConfigurator.Configure()" /></summary>
            void BasicConfiguratorConfigure();

            /// <summary>Calls log4net.LogManager.GetLogger(string)" /></summary>
            object GetLogger(string name);
        }

        #endregion

        #region Nested type: Log4NetRuntime

        [Serializable]
        private class Log4NetRuntime : ILog4NetRuntime
        {
            //private static readonly Type LogManagerType = Type.GetType("log4net.LogManager, log4net");
            private static readonly Type LogManagerType =
                Type.GetType("log4net.Ext.MarshalByRef.MarshalByRefLogManager, log4net");

            private static readonly Type XmlConfiguratorType = Type.GetType("log4net.Config.XmlConfigurator, log4net");

            private static readonly Type BasicConfiguratorType =
                Type.GetType("log4net.Config.BasicConfigurator, log4net");

            private static readonly Delegate GetLoggerByNameDelegate;

            private static readonly Delegate ConfigureDelegate;
            private static readonly Delegate BasicConfigureDelegate;
            private static readonly Delegate ConfigureByFileInfoDelegate;
            private static readonly Delegate ConfigureByFileInfoAndWatchDelegate;

            static Log4NetRuntime()
            {
                GetLoggerByNameDelegate = Log4NetGetLoggerMethodCall<string>();
                ConfigureDelegate = GetMethodCallForConfigure(XmlConfiguratorType, "Configure");
                BasicConfigureDelegate = GetMethodCallForConfigure(BasicConfiguratorType, "Configure");

                ConfigureByFileInfoDelegate = GetMethodCallForConfigureByFileInfo(XmlConfiguratorType, "Configure");
                ConfigureByFileInfoAndWatchDelegate = GetMethodCallForConfigureByFileInfo(XmlConfiguratorType,
                    "ConfigureAndWatch");
            }

            #region 委托生成

            private static Delegate Log4NetGetLoggerMethodCall<TParameter>()
            {
                var stringType = typeof(TParameter);
                var methodInfo = LogManagerType.GetMethod("GetLogger", new[] {stringType});
                var keyParam = Expression.Parameter(stringType, "key");
                var method = Expression.Call(methodInfo, keyParam);
                return Expression.Lambda(method, keyParam).Compile();
            }

            private static Delegate GetMethodCallForConfigure(Type type, string methodName)
            {
                var methodInfo = type.GetMethod(methodName, Type.EmptyTypes);
                var methodCall = Expression.Call(methodInfo);
                return Expression.Lambda(methodCall).Compile();
            }

            private static Delegate GetMethodCallForConfigureByFileInfo(Type type, string methodName)
            {
                var fileInfoParam = Expression.Parameter(typeof(FileInfo), "fileInfo");
                var methodCall = Expression.Call(type.GetMethod(methodName, new[] {typeof(FileInfo)}), fileInfoParam);
                return Expression.Lambda(methodCall, fileInfoParam).Compile();
            }

            #endregion

            #region ILog4NetRuntime Members

            public void XmlConfiguratorConfigure()
            {
                ConfigureDelegate.DynamicInvoke();
            }

            public void BasicConfiguratorConfigure()
            {
                BasicConfigureDelegate.DynamicInvoke();
            }

            public void XmlConfiguratorConfigure(string configFile)
            {
                ConfigureByFileInfoDelegate.DynamicInvoke(new FileInfo(configFile));
            }

            public void XmlConfiguratorConfigureAndWatch(string configFile)
            {
                ConfigureByFileInfoAndWatchDelegate.DynamicInvoke(new FileInfo(configFile));
            }

            public object GetLogger(string name)
            {
                return GetLoggerByNameDelegate.DynamicInvoke(name);
            }

            #endregion
        }

        #endregion
    }

    #endregion

    #region 日志记录器定义

    /// <summary>
    /// Log4NetLogger 定义
    /// </summary>
    // ReSharper disable once ClassTooBig
    [Serializable]
    public class Log4NetLogger : AbstractLogger
    {
        private readonly object _logger;


        static Log4NetLogger()
        {
            IsErrorEnabledDelegate = GetPropertyGetter("IsErrorEnabled");
            IsFatalEnabledDelegate = GetPropertyGetter("IsFatalEnabled");
            IsDebugEnabledDelegate = GetPropertyGetter("IsDebugEnabled");
            IsInfoEnabledDelegate = GetPropertyGetter("IsInfoEnabled");
            IsWarnEnabledDelegate = GetPropertyGetter("IsWarnEnabled");

            ErrorDelegate = GetMethodCallForMessage("Error");
            ErrorExceptionDelegate = GetMethodCallForMessageException("Error");
            ErrorFormatDelegate = GetMethodCallForMessageFormat("ErrorFormat");
            ErrorFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("ErrorFormat");
            FatalDelegate = GetMethodCallForMessage("Fatal");
            FatalExceptionDelegate = GetMethodCallForMessageException("Fatal");
            FatalFormatDelegate = GetMethodCallForMessageFormat("FatalFormat");
            FatalFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("FatalFormat");
            DebugDelegate = GetMethodCallForMessage("Debug");
            DebugExceptionDelegate = GetMethodCallForMessageException("Debug");
            DebugFormatDelegate = GetMethodCallForMessageFormat("DebugFormat");
            DebugFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("DebugFormat");
            InfoDelegate = GetMethodCallForMessage("Info");
            InfoExceptionDelegate = GetMethodCallForMessageException("Info");
            InfoFormatDelegate = GetMethodCallForMessageFormat("InfoFormat");
            InfoFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("InfoFormat");
            WarnDelegate = GetMethodCallForMessage("Warn");
            WarnExceptionDelegate = GetMethodCallForMessageException("Warn");
            WarnFormatDelegate = GetMethodCallForMessageFormat("WarnFormat");
            WarnFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("WarnFormat");

            IsTraceEnabledDelegate = GetPropertyGetter("IsTraceEnabled");
            TraceDelegate = GetMethodCallForMessage("Trace");
            TraceExceptionDelegate = GetMethodCallForMessageException("Trace");
            TraceFormatDelegate = GetMethodCallForMessageFormat("TraceFormat");
            TraceFormatByProviderDelegate = GetMethodCallForMessageFormatByProvider("TraceFormat");
        }

        /// <summary>
        /// </summary>
        /// <param name="logger"></param>
        public Log4NetLogger(object logger)
        {
            _logger = logger;
        }


        protected override void WriteInternal(LogLevel level, object message, Exception exception)
        {
            //重写了 各个方法后就不需要再写这个了，留空就ok
        }

        #region 代理定义

        private static readonly Type LogType = Type.GetType("log4net.ILog, log4net");
        private static readonly Func<object, bool> IsErrorEnabledDelegate;
        private static readonly Func<object, bool> IsFatalEnabledDelegate;
        private static readonly Func<object, bool> IsDebugEnabledDelegate;
        private static readonly Func<object, bool> IsInfoEnabledDelegate;
        private static readonly Func<object, bool> IsWarnEnabledDelegate;

        private static readonly Action<object, object> ErrorDelegate;
        private static readonly Action<object, object, Exception> ErrorExceptionDelegate;
        private static readonly Action<object, string, object[]> ErrorFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> ErrorFormatByProviderDelegate;
        private static readonly Action<object, object> FatalDelegate;
        private static readonly Action<object, object, Exception> FatalExceptionDelegate;
        private static readonly Action<object, string, object[]> FatalFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> FatalFormatByProviderDelegate;
        private static readonly Action<object, object> DebugDelegate;
        private static readonly Action<object, object, Exception> DebugExceptionDelegate;
        private static readonly Action<object, string, object[]> DebugFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> DebugFormatByProviderDelegate;
        private static readonly Action<object, object> InfoDelegate;
        private static readonly Action<object, object, Exception> InfoExceptionDelegate;
        private static readonly Action<object, string, object[]> InfoFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> InfoFormatByProviderDelegate;
        private static readonly Action<object, object> WarnDelegate;
        private static readonly Action<object, object, Exception> WarnExceptionDelegate;
        private static readonly Action<object, string, object[]> WarnFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> WarnFormatByProviderDelegate;

        //The Trace Log Just Support log4net 1.2.13 and later

        private static readonly Func<object, bool> IsTraceEnabledDelegate;
        private static readonly Action<object, object> TraceDelegate;
        private static readonly Action<object, object, Exception> TraceExceptionDelegate;
        private static readonly Action<object, string, object[]> TraceFormatDelegate;
        private static readonly Action<object, IFormatProvider, string, object[]> TraceFormatByProviderDelegate;

        #endregion

        #region 代理-表达式生成

        /// <summary>
        /// 获取属性值代理
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private static Func<object, bool> GetPropertyGetter(string propertyName)
        {
            try
            {
                var funcParam = Expression.Parameter(typeof(object), "logger");
                Expression convertedParam = Expression.Convert(funcParam, LogType);
                Expression property = Expression.Property(convertedParam, propertyName);
                return (Func<object, bool>) Expression.Lambda(property, funcParam).Compile();
            }
            catch
            {
                return null;
            }
        }

        private static Action<object, object> GetMethodCallForMessage(string methodName)
        {
            try
            {
                var loggerParam = Expression.Parameter(typeof(object), "logger");
                var messageParam = Expression.Parameter(typeof(object), "object");
                Expression convertedParam = Expression.Convert(loggerParam, LogType);
                var methodCall = Expression.Call(convertedParam
                    , LogType.GetMethod(methodName, new[]
                    {
                        typeof(object)
                    }), messageParam);

                var targetExpression = Expression.Lambda(methodCall, loggerParam, messageParam);

                return (Action<object, object>) targetExpression.Compile();
            }
            catch
            {
                return null;
            }
        }

        private static Action<object, object, Exception> GetMethodCallForMessageException(string methodName)
        {
            try
            {
                var loggerParam = Expression.Parameter(typeof(object), "logger");
                var messageParam = Expression.Parameter(typeof(object), "object");
                var exceptionParam = Expression.Parameter(typeof(Exception), "exception");
                Expression convertedParam = Expression.Convert(loggerParam, LogType);
                var methodCall = Expression.Call(convertedParam, LogType.GetMethod(methodName, new[]
                {
                    typeof(object),
                    typeof(Exception)
                }), messageParam, exceptionParam);

                var targetExpression = Expression.Lambda(methodCall, loggerParam, messageParam, exceptionParam);

                return (Action<object, object, Exception>) targetExpression.Compile();
            }
            catch
            {
                return null;
            }
        }

        private static Action<object, string, object[]> GetMethodCallForMessageFormat(string methodName)
        {
            try
            {
                var loggerParam = Expression.Parameter(typeof(object), "logger");
                var formatParam = Expression.Parameter(typeof(string), "format");
                var parametersParam = Expression.Parameter(typeof(object[]), "parameters");
                Expression convertedParam = Expression.Convert(loggerParam, LogType);
                var methodCall = Expression.Call(convertedParam, LogType.GetMethod(methodName, new[]
                {
                    typeof(string),
                    typeof(object[])
                }), formatParam, parametersParam);

                var targetExpression = Expression.Lambda(methodCall, loggerParam, formatParam, parametersParam);

                return (Action<object, string, object[]>) targetExpression.Compile();
            }
            catch
            {
                return null;
            }
        }

        private static Action<object, IFormatProvider, string, object[]> GetMethodCallForMessageFormatByProvider(
            string methodName)
        {
            try
            {
                var loggerParam = Expression.Parameter(typeof(object), "logger");
                var providerParam = Expression.Parameter(typeof(IFormatProvider), "formatProvider");
                var formatParam = Expression.Parameter(typeof(string), "format");
                var parametersParam = Expression.Parameter(typeof(object[]), "parameters");
                Expression convertedParam = Expression.Convert(loggerParam, LogType);
                var methodCall = Expression.Call(convertedParam, LogType.GetMethod(methodName, new[]
                {
                    typeof(IFormatProvider),
                    typeof(string),
                    typeof(object[])
                }), providerParam, formatParam, parametersParam);
                var targetExpression = Expression.Lambda(methodCall, loggerParam, providerParam, formatParam,
                    parametersParam);

                return (Action<object, IFormatProvider, string, object[]>) targetExpression.Compile();
            }
            catch
            {
                return null;
            }
        }

        #endregion

        #region ILog Members

        public override bool IsErrorEnabled => IsErrorEnabledDelegate != null && IsErrorEnabledDelegate(_logger);

        public override bool IsFatalEnabled => IsFatalEnabledDelegate != null && IsFatalEnabledDelegate(_logger);

        public override bool IsDebugEnabled => IsDebugEnabledDelegate != null && IsDebugEnabledDelegate(_logger);

        public override bool IsInfoEnabled => IsInfoEnabledDelegate != null && IsInfoEnabledDelegate(_logger);

        public override bool IsWarnEnabled => IsWarnEnabledDelegate != null && IsWarnEnabledDelegate(_logger);

        #endregion

        #region Error

        public override void Error(object message)
        {
            if (IsErrorEnabled)
                ErrorDelegate(_logger, message);
        }

        public override void Error(object message, Exception exception)
        {
            if (IsErrorEnabled)
                ErrorExceptionDelegate(_logger, message, exception);
        }

        public override void ErrorFormat(string format, params object[] args)
        {
            if (IsErrorEnabled)
                ErrorFormatDelegate(_logger, format, args);
        }

        public override void ErrorFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsErrorEnabled)
                ErrorFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion

        #region Fatal

        public override void Fatal(object message)
        {
            if (IsFatalEnabled)
                FatalDelegate(_logger, message);
        }

        public override void Fatal(object message, Exception exception)
        {
            if (IsFatalEnabled)
                FatalExceptionDelegate(_logger, message, exception);
        }

        public override void FatalFormat(string format, params object[] args)
        {
            if (IsFatalEnabled)
                FatalFormatDelegate(_logger, format, args);
        }

        public override void FatalFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsFatalEnabled)
                FatalFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion

        #region Debug

        public override void Debug(object message)
        {
            if (IsDebugEnabled)
                DebugDelegate(_logger, message);
        }

        public override void Debug(object message, Exception exception)
        {
            if (IsDebugEnabled)
                DebugExceptionDelegate(_logger, message, exception);
        }

        public override void DebugFormat(string format, params object[] args)
        {
            if (IsDebugEnabled)
                DebugFormatDelegate(_logger, format, args);
        }

        public override void DebugFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsDebugEnabled)
                DebugFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion

        #region Info

        public override void Info(object message)
        {
            if (IsInfoEnabled)
                InfoDelegate(_logger, message);
        }

        public override void Info(object message, Exception exception)
        {
            if (IsInfoEnabled)
                InfoExceptionDelegate(_logger, message, exception);
        }

        public override void InfoFormat(string format, params object[] args)
        {
            if (IsInfoEnabled)
                InfoFormatDelegate(_logger, format, args);
        }

        public override void InfoFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsInfoEnabled)
                InfoFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion

        #region Warn

        public override void Warn(object message)
        {
            if (IsWarnEnabled)
                WarnDelegate(_logger, message);
        }

        public override void Warn(object message, Exception exception)
        {
            if (IsWarnEnabled)
                WarnExceptionDelegate(_logger, message, exception);
        }

        public override void WarnFormat(string format, params object[] args)
        {
            if (IsWarnEnabled)
                WarnFormatDelegate(_logger, format, args);
        }

        public override void WarnFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsWarnEnabled)
                WarnFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion

        #region Trace

        public override bool IsTraceEnabled => IsTraceEnabledDelegate != null && IsTraceEnabledDelegate(_logger);

        public override void Trace(object message)
        {
            if (IsTraceEnabled)
                TraceDelegate(_logger, message);
        }

        public override void Trace(object message, Exception exception)
        {
            if (IsTraceEnabled)
                TraceExceptionDelegate(_logger, message, exception);
        }

        public override void TraceFormat(string format, params object[] args)
        {
            if (IsTraceEnabled)
                TraceFormatDelegate(_logger, format, args);
        }

        public override void TraceFormat(IFormatProvider formatProvider, string format, params object[] args)
        {
            if (IsTraceEnabled)
                TraceFormatByProviderDelegate(_logger, formatProvider, format, args);
        }

        #endregion
    }

    #endregion
}